# 函数与模块

# 一、代码的重用
# 1.函数的定义：  def 和 return
def fib(n):    # def行列定义函数名，并可以在（）内添加参数
    """Print a Fibonacci series up to n."""    # 通常会在定义函数行下列一行中用""" """来解释这个函数的用途
    a, b = 0, 1     # 这里5-9行的代码行都称为代码块
    while a < n:
        print(a, end=' ')
        a, b = b, a+b
    print()
# 具体再看：
def greet_user():            # 定义一个函数,关键词为"def",函数名为"greet_user",最后以“：”结尾
    """显示简单的问候语"""      # 描述函数的具体功能
    print("Hello!")         # 函数体的代码块，用于实现函数功能

greet_user()                #调用函数
# 函数的特点 ：1.不需要在函数名的后面标明参数也可以 2.不用指定函数参数或返回值的类型
# 代码的重用——应用
# 假设在某一程序中需要使用到以下的代码多次
vowels = set('aeiou')
word = input("Provide a word to search for vowels:")
found = vowels.intersection(set(word))
for vowel in found:
    print(vowel)
# 创建一个函数用来表示以上的所有代码
def search4vowels():     # 为了方便，给代码块取函数名的时候要注意给函数名取个描述性的名字   以及提供一个可选的参数列表，这里的函数没有参数所以为空
    vowels = set('aeiou')        # 以下的所有整个代码块都要向后缩进一个单位
    word = input("Provide a word to search for vowels:")
    found = vowels.intersection(set(word))
    for vowel in found:
        print(vowel)
# 调用参数  注意在调用时要这个函数名以及它的所有参数
search4vowels()
search4vowels()   # 可以多次调用这个函数

# 2.使用注解来改进文档
# 当我们调用函数时，才知道我们需要输入的参数和返回值的类型“type”
# 对此，我们的一种解决办法是把这个信息增加到docstring（描述性的注释）
# 一般上 我们使用三重引号来docstring，即 '''   '''  (这种类型的引号可以跨越多行)
def search4vowels():
    '''display any vowels foun in an asked-for word'''    # 通过注释我们就能够知道这个函数的参数是str（字符串）
    vowels = set('aeiou')
    word = input("Provide a word to search for vowels:")
    found = vowels.intersection(set(word))
    for vowel in found:
        print(vowel)
search4vowels()

# 3.函数接受参数
# 不在提示窗口中提示用户输入一个搜索词，而是直接在函数名的小括号内直接指定参数
def search_vowels(word):
    ''' display any vowels found in supplied word '''    # 通过注释我们就能够知道这个函数的参数是str（字符串）
    vowels = set('aeiou')
    found = vowels.intersection(set(word))
    for vowel in found:
        print(vowel)
search_vowels("python")  # --> o    这里相当于是在调用这个函数并直接指定出具体的参数

# ！！！ True & False    判断值的真假
# python中每一个对象都有一个关联的真值，表示这个对象计算为True或False
# 补充内置函数bool：可以处理任何python对象，并返回True或False
# 计算为0，值为None，空串，或一个空的内置数据结构，则为False
bool(0)
bool(0.0)
bool('')
bool([])
bool({})
bool(None)
#  -----> False
# python中计算所有其他的对象（除了上述的几种情况外）都为True
bool(4)
bool(-1)
bool(0.2)
bool('asd')
bool([0,2,5,6])
# -----> True

# 4.返回一个值 return
def search_vowels(word):
    '''display any vowels found in supplied word'''
    vowels = set('aeiou')
    found = vowels.intersection(set(word))
    return bool(found)   # 添加这行代码
search_vowels('mikolo')   # --->True   如果找到元音字母就会返回True
search_vowels('dgfhtr')   # --->False  如果没找到元音字母就会返回False

# 返回多个值
# 让函数返回多个值（放在一个集合中），而不是只是一个布尔值，只需要把bool删去
def search_vowels(word):
    '''return a boolean based on any vowels found '''
    vowels = set('aeiou')
    found = vowels.intersection(set(word))
    return found     # 结果作为一个数据结构（集合）返回
# 还可以再简化为：去掉不必要的found
def search_vowel(word):
    '''return a boolean based on any vowels found'''
    vowel = set('aeiou')
    return vowel.intersection(set(word))
search_vowel('shdureoi')  # ---》{'e', 'i', 'o', 'u'}
search_vowel('gghytr')   # ---> set{} 这表示的是返回一个空集合

# !!!区分四种不同的内置函数结构（BIF）的空值表示
a = list()
print(a)   # --->[]空列表

b = dict()
print(b)   # --->{}空字典

c = tuple()
print(c)  # --->()空元组

d = set()
print(d)  # --->set()空集合

# 5.使用注解来改进文档
# 函数注解是可选的。即可更改
# 函数注解可以提供信息。
# 使用方法如下 ：       小括号内参数的后面添加一个:  再跟上所期望的参数的类型， 函数名的后面用 ->  再跟上所期望返回值的类型
def search4vowels(word:str) ->set:     # word是字符串类型  返回值是一个集合
    """Return a boolean based on any vowels found"""
    vowels = set('aeiou')
    return vowels.intersection(set(word))
# 查看注解而不读函数代码的方法：help
help(search4vowels) # 以下为返回的结果：
# Help on function search4vowels in module __main__:
# search4vowels(word: str) -> set
#     Return a boolean based on any vowels found
# 总结 函数的注解可以用来描述函数参数的类型以及函数的返回值的类型

# 通用函数  ---允许用户指定要搜索的字母集合 而不只是五个元音字母
def search4vowels(phrase:str) ->set:
    """Return a boolean based on any vowels found"""
    vowels = set('aeiou')
    return vowels.intersection(set(phrase))
# 上下对比
def search4letters(phrase:str,letters:str) ->set:
    """Return  any vowels found in a supplied phrase."""
    return set(letters).intersection(set(phrase))   # 本质上就是返回两个集合的交集 set（letters）交set（phrase）
search4letters('aeiou','hjick-nvcu')   # --->{'i', 'u'}
search4letters('lifeob','ca')   # --->set()
# 这个set内置函数调用由letters变量中的字符创建一个集合对象。不需要把这个集合赋给新变量而是直接使用，所以我们在它的后面使用.来调用方法
# intersection方法取其参数（phrase）中包含的字符集合，与已有的集合对象（letters）求交集
help(search4letters)   # 查看search4letters的注解

# 6.为参数指定默认值
# 调用函数的代码时没有提供其他值(一般上是指第二个参数)，就会自动使用这个默认值
def search4letters(phrase:str,letters:str='aeiou') ->set:  # 为letters赋了一个默认值"aeiou",如果调用代码块没有提供替代值，就会使用这个默认值
    """Return  any vowels found in a supplied phrase."""
    return set(letters).intersection(set(phrase))
search4letters('life,the universe,and everything')         # 没有给出第二个参数，所以使用了默认的参数集合
search4letters('life,the universe,and everything','aeiou')     # 这两个结果都是一样的--->{'a', 'e', 'i', 'u'}

# 7.位置赋值 & 关键字赋值
# 位置赋值：要特别注意参数的顺序，按照参数的顺序进行赋值    考虑顺序的就是位置赋值
# 在上面的例子中，字符串"life,the universe,and everything"对应的是参数phrase ，字符串“aeiou”对应的是参数letters，

# 关键字赋值：调用函数时可以按任意的顺序将各个字符串赋给相应的参数名      不用考虑位置
def search4letters(phrase:str,letters:str='aeiou') ->set:
    """Return  any vowels found in a supplied phrase."""
    return set(letters).intersection(set(phrase))
search4letters(letters='xyz',phrase='galaxz')     # --->{'x', 'z'}


# 函数补充：
# 按值调用语义
def double(arg):     # 这个函数把传入的值加倍
    print("before:",arg)
    arg = arg*2
    print("after:",arg)

num = 10      # --->  before: 10
double(num)   # ---> after: 20
print(num)    #  ---> 10

saying = "hello"    # ---> before: hello
double(saying)      # ---> after: hellohello
print(saying)      # ---> hello

numbers = [42,56,89] # ---> before: [42, 56, 89]
double(numbers)      # ---> after: [42, 56, 89, 42, 56, 89]
print(numbers)       # ---> [42, 56, 89]

# 按引用调用语义
def change(arg):          # 这个函数为传入的列表添加一个字符串
    print("before:",arg)
    arg.append("More data")
    print("after:",arg)

numbers = [42,56,89]     # ---> before: [42, 56, 89]
change(numbers)          # ---> after: [42, 56, 89, 'More data']
print(numbers)           # ---> [42, 56, 89, 'More data']

# 如果变量的数据类型是一个可变，则会按引用调用语义； 如果变量的数据类型是一个不变的，则会按值调用语义
# 总结：列表、字典、集合都是可变的 ； 元组、整数、字符串都是不可变的

